package com.jacklin.snowy.limitstrategy.service.impl;

import com.jacklin.snowy.limitstrategy.annotation.RequestLimiter;
import com.jacklin.snowy.limitstrategy.constants.RedisKeyConstant;
import com.jacklin.snowy.limitstrategy.dto.RequestLimitDTO;
import com.jacklin.snowy.limitstrategy.enums.RequestLimitType;
import com.jacklin.snowy.limitstrategy.service.RequestLimitService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalTime;
import java.util.UUID;

/**
 * 滑动窗口 限流
 *
 * @author: jacklin
 * @since: 2022/5/10 14:11
 */
@Slf4j
@Service
public class SlideWindowRateLimitServiceImpl implements RequestLimitService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public boolean checkRequestLimit(RequestLimitDTO dto) {
        String key = RedisKeyConstant.RequestLimit.QPS_SLIDE_WINDOW + dto.getKey();
        RequestLimiter limiter = dto.getLimiter();
        long current = System.currentTimeMillis();
        long duringTime = limiter.unit().toMillis(limiter.time());
        Long count = redisTemplate.opsForZSet().count(key, current - duringTime, current);
        // 清除有效期外的数据
        redisTemplate.opsForZSet().removeRangeByScore(key, 0, current - duringTime - 1f);

        log.info("限流配置：{} {} 内允许访问 {} 次", limiter.time(), limiter.unit(), limiter.limitCount());
        log.info("访问时间【{}】", LocalTime.now());
        // 检测是否到达限流值
        if (count != null && count >= limiter.limitCount()) {
            String msg = "【" + key + "】限流控制，" + limiter.time() + " " + limiter.unit().name() + "内只允许访问 " + limiter.limitCount() + " 次";
            log.info(msg);
            return true;
        } else {
            redisTemplate.opsForZSet().add(key, UUID.randomUUID().toString(), current);
            log.info("未达到限流值，放行 {}/{}", count, limiter.limitCount());
            return false;
        }
    }

    @Override
    public RequestLimitType getType() {
        return RequestLimitType.SLIDE_WINDOW;
    }
}
